package com.wewins.fota.common.util;

import com.wewins.fota.bean.bo.excel.CellSource;
import com.wewins.fota.bean.bo.excel.ExcelHeaderItem;
import com.wewins.fota.bean.bo.excel.ExportData;
import com.wewins.fota.bean.bo.excel.RowData;
import org.apache.commons.io.FileUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class ExcelUtil {
    private static final String EXCEL_XLS = "xls";
    private static final String EXCEL_XLSX = "xlsx";
    private static final int START_ROW_INDEX = 0;
    private static final int START_CELL_INDEX = 0;
    private static final int MODE_NEW = 0;
    private static final int MODE_ADD = 1;

    private Workbook workbook;
    private File excelFile;
    private int writeMode = MODE_ADD;

    private ExcelUtil(File file) {
        this.excelFile = file;
        this.workbook = getWorkbook(file);
    }

    public static synchronized ExcelUtil builder(String filePath) {
        return builder(new File(filePath));
    }

    public static synchronized ExcelUtil builder(File file) {
        return new ExcelUtil(file);
    }

    public ExcelUtil withWriteMode(int mode) {
        this.writeMode = mode;
        return this;
    }

    public <T extends RowData> void write(List<ExcelHeaderItem> headers, List<T> data) throws IOException {
        String name = excelFile.getName();
        String sheetName = name.substring(0, name.lastIndexOf("."));
        Sheet sheet = getSheet(sheetName);
        FileUtil.createFile(excelFile);
        FileOutputStream out = null;
        try {
            writeHeader(sheet, headers);
            CellSource source = getCellSource(workbook, sheet);
            writeData(source, data);
            out = FileUtils.openOutputStream(excelFile);
            workbook.write(out);
        } finally {
            IOUtil.close(out);
            IOUtil.close(workbook);
        }
    }

    private CellSource getCellSource(Workbook workbook, Sheet sheet) {
        return CellSource.builder().workbook(workbook).sheet(sheet).build();
    }

    private <T extends RowData> void writeData(CellSource source, List<T> data) {
        Sheet sheet = source.getSheet();
        AtomicInteger rowIndex = new AtomicInteger(sheet.getLastRowNum() + 1);
        data.forEach(rowData -> {
            Row row = sheet.createRow(rowIndex.getAndIncrement());
            AtomicInteger colIndex = new AtomicInteger(START_CELL_INDEX);
            rowData.getColumns().forEach(colData -> {
                int index = colIndex.getAndIncrement();
                source.setCell(row.createCell(index, colData.getCellType()));
                colData.fillCell(source);
            });
        });
    }

    private void writeHeader(Sheet sheet, List<ExcelHeaderItem> headers) {
        int lastRowNum = sheet.getLastRowNum();
        if (lastRowNum == -1) { // new sheet will write header.
            Row row = sheet.createRow(START_ROW_INDEX);
            AtomicInteger colIndex = new AtomicInteger(START_CELL_INDEX);
            headers.forEach(item -> {
                int index = colIndex.getAndIncrement();
                sheet.setColumnWidth(index, item.getWidth() * 256);
                Cell cell = row.createCell(index);
                cell.setCellValue(item.getName());
            });
        }
    }

    private Sheet getSheet(String sheetName) {
        int sheetIndex = workbook.getSheetIndex(sheetName);
        if (sheetIndex != -1) {
            if (writeMode == MODE_ADD) {
                return workbook.getSheetAt(sheetIndex);
            }
            workbook.removeSheetAt(sheetIndex);
        }
        return workbook.createSheet(sheetName);
    }

    private Workbook getWorkbook(File file) {
        Workbook wb = null;
        String name = file.getName();
        // when file is big enough, will throw IOException, need set ratio.
        ZipSecureFile.setMinInflateRatio(-1.0d);
        if (name.endsWith(EXCEL_XLS)) {     //Excel&nbsp;2003
            try {
                wb = new HSSFWorkbook(new FileInputStream(file));
            } catch (IOException e) {
                wb = new HSSFWorkbook();
            }
        } else if (name.endsWith(EXCEL_XLSX)) {    // Excel 2007/2010
            try {
                wb = new XSSFWorkbook(new FileInputStream(file));
            } catch (IOException e) {
                wb = new XSSFWorkbook();
            }
        }
        return wb;
    }

    public void writeWithNew(ExportData data) throws IOException {
        writeWithNew(data.getHeaders(), data.getRows());
    }

    public void writeWithAdd(ExportData data) throws IOException {
        writeWithAdd(data.getHeaders(), data.getRows());
    }


    public <T extends RowData> void writeWithAdd(List<ExcelHeaderItem> headers, List<T> data) throws IOException {
        this.withWriteMode(MODE_ADD).write(headers, data);
    }

    public <T extends RowData> void writeWithNew(List<ExcelHeaderItem> headers, List<T> data) throws IOException {
        this.withWriteMode(MODE_NEW).write(headers, data);
    }

}
